﻿#ifdef WIN32
#include "WindowsPlatform.h"
#include "Platform/Window/WindowsWindow.h"

#include "Platform/Windows/WindowsThread.h"
#include <Windows.h>
#include <windowsx.h>
#include <winerror.h>

#include <iostream>

static CWindowsPlatform* GPlatform=nullptr;


CWindowsPlatform::CWindowsPlatform()
{
    GPlatform = this;

    WindowThread=new CWindowsOSThread();

}

CWindowsPlatform::~CWindowsPlatform()
{
    delete WindowThread;
}

CWindowsPlatform *CWindowsPlatform::Get()
{
    return GPlatform;
}

int64_t CWindowsPlatform::CreateProcessWithoutBlock(const CString &Command, const CString &WorkingDirectory)
{
    auto CommandInWChar = Command.ToUtf16();  
    auto WorkingDirectoryInWChar = WorkingDirectory.ToUtf16();

    
    STARTUPINFOW StartupInfo;
    PROCESS_INFORMATION ProcessInfo;

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof(StartupInfo);
    ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));

    if (!CreateProcessW(nullptr,
         (LPWSTR)CommandInWChar.Data(), 
         nullptr, 
         nullptr, 
         FALSE, 
         CREATE_NO_WINDOW, 
         nullptr, 
         (LPWSTR)WorkingDirectoryInWChar.Data(), 
         &StartupInfo, 
         &ProcessInfo))
    {
        auto Error=GetLastError();

        return -1;
    }

    return (int64_t)ProcessInfo.hProcess;
}

void CWindowsPlatform::KillProcess(int64_t ProcessID)
{
    HANDLE ProcessHandle = (HANDLE)ProcessID;
    TerminateProcess(ProcessHandle, 0);
    CloseHandle(ProcessHandle);
}

TVector<const char *> CWindowsPlatform::GetVulkanInstanceExtensions() const
{
    TVector<const char *> Extensions;

    Extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME);
    Extensions.Add("VK_KHR_win32_surface");

    return Extensions;

}

int64_t CWindowsPlatform::CreateWindowsProcess(const CString &Command, const CString &WorkingDirectory)
{
    auto CommandInWChar = Command.ToUtf16();  
    auto WorkingDirectoryInWChar = WorkingDirectory.ToUtf16();

    
    STARTUPINFOW StartupInfo;
    PROCESS_INFORMATION ProcessInfo;

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof(StartupInfo);
    ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));

    if (!CreateProcessW(nullptr,
         (LPWSTR)CommandInWChar.Data(), 
         nullptr, 
         nullptr, 
         FALSE, 
         0, 
         nullptr, 
         (LPWSTR)WorkingDirectoryInWChar.Data(), 
         &StartupInfo, 
         &ProcessInfo))
    {
        auto Error=GetLastError();

        return -1;
    }


    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);

    //get the process exit code
    DWORD ExitCode=0;
    BOOL Succeed=GetExitCodeProcess(ProcessInfo.hProcess, &ExitCode);

    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);

    //convert the exit code to int64_t
    return (int64_t)ExitCode;
}

TOptional<SystemEvent> CWindowsPlatform::PopEvents()
{
    return WindowThread->PopEvent();

    // if(!EventQueue.empty())
    // {
    //     SystemEvent Event = EventQueue.front();
    //     EventQueue.pop();
    //     return Event;
    // }

    // MSG msg = { 0 };
    // if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    // {
    //     //translate the message and dispatch it to the window procedure
    //     TranslateMessage(&msg);
    //     DispatchMessage(&msg);

    //     if(CWindowsProcedure::bWaitingNextChar)
    //     {
    //         //dispatch the next char message
    //         TranslateMessage(&msg);
    //         DispatchMessage(&msg);
    //     }


    //     if(!EventQueue.empty())
    //     {
    //         SystemEvent Event = EventQueue.front();
    //         EventQueue.pop();
    //         return Event;
    //     }
    // }

    // return TOptional<SystemEvent>();
}

ISytemWindowInterface *CWindowsPlatform::CreateSystemWindow()
{
    return WindowThread->CreateWindowWithBlock();

}

void CWindowsPlatform::DestroySystemWindow(ISytemWindowInterface *Window)
{
    WindowThread->DestroyWindowWithBlock(reinterpret_cast<CWindowsWindow*>(Window));
}

SRect CWindowsPlatform::GetDisplayWorkArea(const SIntPoint &InPosition) const
{
    POINT Point;
    Point.x = InPosition.X;
    Point.y = InPosition.Y;

    MONITORINFO MonitorInfo;
    MonitorInfo.cbSize = sizeof(MonitorInfo);

    HMONITOR Monitor = MonitorFromPoint(Point, MONITOR_DEFAULTTONEAREST);
    GetMonitorInfo(Monitor, &MonitorInfo);

    SRect Result;
    Result.Min.X = MonitorInfo.rcWork.left;
    Result.Min.Y = MonitorInfo.rcWork.top;
    Result.Extent.X = MonitorInfo.rcWork.right - MonitorInfo.rcWork.left;
    Result.Extent.Y = MonitorInfo.rcWork.bottom - MonitorInfo.rcWork.top;

    return Result;
}

void CWindowsPlatform::StartTextInput(uint64_t WindowID, SIntPoint IMEWidgetPosition)
{
    WindowThread->StartTextInput(WindowID, IMEWidgetPosition);
}

void CWindowsPlatform::StopTextInput()
{
    WindowThread->StopTextInput();
}

bool CWindowsPlatform::IsTextInputStarted() const
{
    return WindowThread->IsTextInputStarted();
}

void CWindowsPlatform::UseSystemCursor(EMouseCursorType CursorType)
{
    WindowThread->UseSystemCursor(CursorType);
}

#include <ShlObj.h>

TVector<TTuple<CString, SPath>> CWindowsPlatform::GetDefaultFavoriteList()
{
    TVector<TTuple<CString, SPath>> Result;

    //find download path
    {
        PWSTR path;
        HRESULT result = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path);
        if (result == S_OK) {
            TTuple<CString, SPath> Download;
            Download.First=U"下载";
            Download.Second=CString((char16_t*)path);
            Result.Add(Download);
        }
        CoTaskMemFree(path);
    }

    //find desktop path
    {
        PWSTR path;
        HRESULT result = SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &path);
        if (result == S_OK) {
            TTuple<CString, SPath> Desktop;
            Desktop.First = U"桌面";
            Desktop.Second = CString((char16_t*)path);
            Result.Add(Desktop);
        }
        CoTaskMemFree(path);
    }

    //find document path
    {       
        PWSTR path;
        HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &path);
        if (result == S_OK) {
            TTuple<CString, SPath> Desktop;
            Desktop.First = U"文档";
            Desktop.Second = CString((char16_t*)path);
            Result.Add(Desktop);
        }
        CoTaskMemFree(path);
    }

    //find all disk
    DWORD drives = GetLogicalDrives();
    for (int i = 0; i < 26; i++) {
        if (drives & 1) {
            TTuple<CString, SPath> Disk;
           
            Disk.Second= SPath(CString((char32_t)(U'A'+i))+U":\\");

            //get Volume name
            WCHAR VolumeName[256];
            WCHAR Path[]=L"C:\\";
            Path[0]= L'A'+i;
            BOOL bSucceeded = GetVolumeInformationW(Path,
                                        VolumeName,
                                        256,
                                        NULL,
                                        NULL,
                                        NULL,
                                        NULL,
                                        0);
            Disk.First=CString((char16_t*)VolumeName)+CString(U"(") +CString((char32_t)(U'A'+i))+U":"+CString(U")");
        
            Result.Add(Disk);
        }
        drives >>= 1;
    }

    return Result;

}

bool CWindowsPlatform::IsKeyDown(EKey Key)
{
    return (GetKeyState(CWindowsOSThread::ToWindowsKey(Key)) & 0x8000) != 0;
}

void CWindowsPlatform::SetClipboardText(const CString &Text)
{
    auto TextInWChar = Text.ToUtf16();
    if (OpenClipboard(NULL))
    {
        EmptyClipboard();
        HGLOBAL hClipboardData;
        hClipboardData = GlobalAlloc(GMEM_DDESHARE, (TextInWChar.Num()+1) * sizeof(char16_t));
        char16_t * pchData;
        pchData = (char16_t*)GlobalLock(hClipboardData);
        memcpy(pchData, TextInWChar.Data(), TextInWChar.Num() * sizeof(char16_t));
        pchData[TextInWChar.Num()] = 0;
        GlobalUnlock(hClipboardData);
        SetClipboardData(CF_UNICODETEXT, hClipboardData);
        CloseClipboard();
    }
}

bool CWindowsPlatform::HasClipboardText()
{
    return IsClipboardFormatAvailable(CF_UNICODETEXT);
}

CString CWindowsPlatform::GetClipboardText()
{
    if (OpenClipboard(NULL))
    {
        HANDLE hClipboardData = GetClipboardData(CF_UNICODETEXT);
        char16_t *pchData = (char16_t*)GlobalLock(hClipboardData);
        CString Text = CString(pchData);
        GlobalUnlock(hClipboardData);
        CloseClipboard();
        return Text;
    }

    return CString();
}

void CWindowsPlatform::CaptureMouse(uint64_t WindowID)
{
    WindowThread->CaptureMouse(WindowID);
}

void CWindowsPlatform::ReleaseMouseCapture()
{
    WindowThread->ReleaseMouseCapture();
}

CString CWindowsPlatform::SystemStringToUnicode(const char * InAnsiChar) const
{
    //convert ansi char to utf32 char
    int32_t Length = MultiByteToWideChar(CP_ACP, 0, InAnsiChar, -1, NULL, 0);
    if (Length == 0)
    {
        return CString();
    }

    TVector<char16_t> Utf16Char;
    Utf16Char.Reserve(Length);
    MultiByteToWideChar(CP_ACP, 0, InAnsiChar, -1, (LPWSTR)Utf16Char.Data(), Length);

    return CString(Utf16Char.Data());
}


#endif